#define _GNU_SOURCE

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <gtk/gtk.h>
#include <webkit/webkit.h>


#define ASIZE(X) (sizeof(X) / sizeof((X)[0]))
#define STARTS_WITH(str, prefix) (strstr((str), (prefix)) == (str))
#define IS_TRUE(str) (STARTS_WITH((str), "true") || STARTS_WITH((str), "yes"))
#define STR_EQ(str1, str2) (strcmp((str1), (str2)) == 0)
#define NEWA(TYPE, COUNT) ((TYPE *) malloc(sizeof(TYPE) * COUNT))

#define OUTSTREAM stdout
#define ERRSTREAM stdout

typedef struct {
	int id;
	WebKitWebView * web_view;
	GtkWidget * browser;
	GtkWidget * window;
	int history_limit;
} WebWindow;

typedef struct {
	WebWindow * windows;
	int nwins;
	char line[1000];
	int position;
	int timeout;
} WebGui;

void output(int id, const char * event, const char * format, ...){
	va_list ap;
	
	if(id >= 0){
		fprintf(OUTSTREAM, "%d", id);
	} else {
		fprintf(OUTSTREAM, "*");
	}
	fprintf(OUTSTREAM, " %s", event);
	if(format){
		fprintf(OUTSTREAM, " ");
		va_start(ap, format);
		vfprintf(OUTSTREAM, format, ap);
		va_end(ap);
	}
	fprintf(OUTSTREAM, "\n");
	fflush(OUTSTREAM);
}

void error(const char * format, ...){
	va_list ap;
	
	fprintf(ERRSTREAM, "* ERROR ");
	va_start(ap, format);
	vfprintf(ERRSTREAM, format, ap);
	va_end(ap);
	fprintf(ERRSTREAM, "\n");
	fflush(ERRSTREAM);
}

int get_back_steps(WebWindow * window){
	WebKitWebBackForwardList * bflist;
	
	bflist = webkit_web_view_get_back_forward_list(window->web_view);
	return webkit_web_back_forward_list_get_back_length(bflist);
}
static void link_hover_cb (WebKitWebView * web_view, const gchar* title, const gchar* link, gpointer data){
	output(((WebWindow *) data)->id, "HOVER", (title && link) ? "%s %s" : NULL, link, title);
}

static void title_change_cb (WebKitWebView* web_view, WebKitWebFrame* web_frame, const gchar* title, gpointer data){
	output(((WebWindow *) data)->id, "TITLE", "%s", title);
}

/*
static void progress_change_cb (WebKitWebView * web_view, gint progress, gpointer data){
	if(progress == 100) output(((WebWindow *) data)->id, "FINISHED", NULL);
}
*/

static void load_finished_cb(WebKitWebView * web_view, WebKitWebFrame * web_frame, gpointer data){
	output(((WebWindow *) data)->id, "FINISHED", "%s", webkit_web_frame_get_uri(web_frame));
}

static void load_commit_cb (WebKitWebView * web_view, WebKitWebFrame* frame, gpointer data){
	output(((WebWindow *) data)->id, "COMMIT", "%s", webkit_web_frame_get_uri(frame));	
}

static void status_changed_cb(WebKitWebView * web_view, gchar * status, gpointer data){
	output(((WebWindow *) data)->id, "STATUS", "%s", status);
}

static void destroy_cb (GtkWidget* widget, gpointer data){
	gtk_main_quit();
	exit(0);
}

gboolean timer_cb(gpointer data){
	output(-1, "TIMER", NULL);
	return ((WebGui *) data)->timeout > 0;
}

void command(WebGui * gui){
	char * cmd;
	int id;
	WebWindow * window;
	
	if(strlen(gui->line) == 0) return;
	if(STR_EQ(gui->line, "exit")){
		gtk_main_quit();
		return;
	} else if(STARTS_WITH(gui->line, "echo")){
		output(-1, "ECHO", "%s", gui->line + 5);
		return;
	} else if(STARTS_WITH(gui->line, "timer")){
		gui->timeout = atoi(gui->line + 6);
		if(gui->timeout > 0) gtk_timeout_add(gui->timeout, timer_cb, gui);
		return;
	}
	cmd = gui->line;
	while(*cmd && (*cmd != ' ')) cmd++;
	if(!*cmd){
		error("Command does not conform expected syntax \"<id> <command> <arg0> <arg1> ...\"");
		return;
	}
	*(cmd++) = '\0';
	if(sscanf(gui->line, "%d", &id) != 1){
		error("Command does not conform expected syntax \"<id> <command> <arg0> <arg1> ...\"");
		return;
	}
	if((id < 0) || (id >= gui->nwins)){
		error("Window id out of range");
		return;
	}
	window = &(gui->windows[id]);
	
	if(STARTS_WITH(cmd, "open")){
		webkit_web_view_open(window->web_view, (gchar *)(cmd + 5));
	} else if(STARTS_WITH(cmd, "clearhistory")){
		int offset;
		offset = atoi(cmd + 13);
		window->history_limit = get_back_steps(window) + offset;
	} else if(STARTS_WITH(cmd, "back")){
		if(window->history_limit < get_back_steps(window))
			webkit_web_view_go_back(window->web_view);
	} else if(STARTS_WITH(cmd, "forward")){
		webkit_web_view_go_forward(window->web_view);
	} else if(STARTS_WITH(cmd, "stop")){
		webkit_web_view_stop_loading(window->web_view);
	} else if(STARTS_WITH(cmd, "reload")){
		webkit_web_view_reload(window->web_view);
	} else if(STARTS_WITH(cmd, "exec")){
		webkit_web_view_execute_script(window->web_view, (gchar *)(cmd + 5));
	} else if(STARTS_WITH(cmd, "size")){
		int w, h, rw, rh;
		if(sscanf(cmd + 5, "%d %d", &w, &h) == 2){
			gtk_window_resize(GTK_WINDOW(window->window), w, h);
			gtk_window_get_size(GTK_WINDOW(window->window), &rw, &rh);
			output(id, "SIZE", "%d %d", rw, rh);
		} else {
			error("Wrong arguments to size (2 decimal integers expected)");
		}
	} else if(STARTS_WITH(cmd, "position")){
		int x, y;
		if(sscanf(cmd + 9, "%d %d", &x, &y) == 2){
			gtk_window_move(GTK_WINDOW(window->window), x, y);
		} else {
			error("Wrong arguments to position (2 decimal integers expected)");
		}
	} else if(STARTS_WITH(cmd, "visible")){
		if(IS_TRUE(cmd + 8)){
			gtk_widget_show(window->window);
		} else {
			gtk_widget_hide(window->window);
		}
	} else if(STARTS_WITH(cmd, "zindex")){
		if(STARTS_WITH(cmd + 7, "top")){
			gtk_window_set_keep_below(GTK_WINDOW(window->window), FALSE);
			gtk_window_set_keep_above(GTK_WINDOW(window->window), TRUE);
		} else if(STARTS_WITH(cmd + 7, "bottom")){
			gtk_window_set_keep_above(GTK_WINDOW(window->window), FALSE);
			gtk_window_set_keep_below(GTK_WINDOW(window->window), TRUE);
		} else {
			gtk_window_set_keep_above(GTK_WINDOW(window->window), FALSE);
			gtk_window_set_keep_below(GTK_WINDOW(window->window), FALSE);
		}
	} else if(STARTS_WITH(cmd, "fullscreen")){
		if(IS_TRUE(cmd + 11)){
			gtk_window_fullscreen(GTK_WINDOW(window->window));
		} else {
			gtk_window_unfullscreen(GTK_WINDOW(window->window));
		}
	} else if(STARTS_WITH(cmd, "scroll")){
		GtkPolicyType policy = (IS_TRUE(cmd + 7)) ? GTK_POLICY_AUTOMATIC : GTK_POLICY_NEVER;
		gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(window->browser), policy, policy);
	} else {
		error("Unknown command: %s\n", cmd);
	}
}

void input_buffer(char * buffer, int size, WebGui * gui){
	int i;
	char c;
	
	for(i = 0; i < size; i++){
		c = buffer[i];
		if(c == '\n'){
			command(gui);
			gui->position = 0;
			memset(gui->line, 0, ASIZE(gui->line));
		} else {
			if(i < ASIZE(gui->line) - 1) gui->line[gui->position++] = c;
		}
	}
}

void input(gpointer data, gint fd, GdkInputCondition condition){
	char buffer[100];
	ssize_t count;
	WebGui * gui;
	
	gui = (WebGui *) data;
	count = read(fd, buffer, ASIZE(buffer));
	if(count == 0){
		gtk_main_quit();
		return;
	}
	input_buffer(buffer, count, gui);
}

void process_arg_lines(int argc, char ** argv, char ** buffer, size_t * size, int * nwins){
	FILE * stream;
	const char * arg;
	int i;
	
	stream = open_memstream(buffer, size);
	for(i = 1; i < argc; i++){
		arg = argv[i];
		if(!strlen(arg)) continue;
		if(arg[0] == '-'){
			*nwins = atoi(arg + 1);
			if(*nwins < 1) *nwins = 1;
		} else {
			fputs(arg, stream);
			fputc('\n', stream);
		}
	}
	fclose(stream);
}

void screen_size(GdkScreen * screen, int * width, int * height){
	if(!screen) screen = gdk_screen_get_default();
	*width = gdk_screen_get_width(screen);
	*height = gdk_screen_get_height(screen);
}

void init_webgui(WebGui * gui, int nwins){
	GtkWidget * vbox;
	int i, width, height;
	WebWindow * window;
	
	screen_size(NULL, &width, &height);
	output(-1, "SCREEN", "%d %d", width, height);
	
	gui->nwins = nwins;
	memset(gui->line, 0, ASIZE(gui->line));
	gui->position = 0;
	gui->timeout = 0;
	
	gui->windows = NEWA(WebWindow, nwins);
	for(i = 0; i < nwins; i++){
		window = &(gui->windows[i]);
		window->id = i;
		window->history_limit = 0;
		
		vbox = gtk_vbox_new(FALSE, 0);
		
		window->browser = gtk_scrolled_window_new(NULL, NULL);
		gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(window->browser), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
		window->web_view = WEBKIT_WEB_VIEW(webkit_web_view_new());
		gtk_container_add(GTK_CONTAINER(window->browser), GTK_WIDGET(window->web_view));
		g_signal_connect(G_OBJECT(window->web_view), "title-changed", G_CALLBACK(title_change_cb), window);
		// g_signal_connect(G_OBJECT(window->web_view), "load-progress-changed", G_CALLBACK(progress_change_cb), window);
		g_signal_connect(G_OBJECT(window->web_view), "load-committed", G_CALLBACK(load_commit_cb), window);
		g_signal_connect(G_OBJECT(window->web_view), "load-finished", G_CALLBACK(load_finished_cb), window);
		g_signal_connect(G_OBJECT(window->web_view), "hovering-over-link", G_CALLBACK(link_hover_cb), window);
		g_signal_connect(G_OBJECT(window->web_view), "status-bar-text-changed", G_CALLBACK(status_changed_cb), window);
		
		gtk_box_pack_start(GTK_BOX(vbox), window->browser, TRUE, TRUE, 0);
		
		window->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
		gtk_window_set_decorated(GTK_WINDOW(window->window), FALSE);
		gtk_window_set_keep_below(GTK_WINDOW(window->window), TRUE);
		screen_size(gtk_window_get_screen(GTK_WINDOW(window->window)), &width, &height);
		gtk_window_set_default_size(GTK_WINDOW(window->window), width, height);
		gtk_window_move(GTK_WINDOW(window->window), 0, 0);
		gtk_widget_set_name(window->window, "WebGui");
		g_signal_connect(G_OBJECT(window->window), "destroy", G_CALLBACK(destroy_cb), NULL);
		
		gtk_container_add(GTK_CONTAINER(window->window), vbox);
		gtk_widget_grab_focus(GTK_WIDGET(window->web_view));
		gtk_widget_show_all(window->window);
		// gtk_widget_hide(window->window);
	}
}

int main (int argc, char ** argv){
	WebGui gui;
	int nwins = 1;
	char * arg_buffer = NULL;
	size_t arg_buffer_size = 0;
	
	gtk_init(&argc, &argv);
	process_arg_lines(argc, argv, &arg_buffer, &arg_buffer_size, &nwins);
	init_webgui(&gui, nwins);
	output(-1, "STARTED", "%d window(s)", nwins);
	input_buffer(arg_buffer, arg_buffer_size, &gui);
	gdk_input_add(0, GDK_INPUT_READ, input, &gui);
	gtk_main();
	return 0;
}


